use crate::model::config::Config;
use crate::model::enum_type::{ArchType, OsType};
use crate::shells::Shell;
use color_eyre::config::HookBuilder;
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::ExecutableCommand;
use lazy_static::lazy_static;
use ratatui::backend::{Backend, CrosstermBackend};
use ratatui::Terminal;
use serde::Serialize;
use std::env;
use std::ffi::OsString;
use std::io::stdout;
use std::path::PathBuf;

lazy_static! {
    static ref VCH_DIR: PathBuf = home::home_dir().unwrap().join(PathBuf::from(".vch")); // 默认的vch目录
    static ref CONFIG_DIR: PathBuf = VCH_DIR.join(".config"); // 配置文件目录
    static ref CONFIG_FILE: PathBuf = CONFIG_DIR.join("config.toml");// 配置文件
    static ref DEFAULT_VERSION_DIR: PathBuf = VCH_DIR.join("versions");// 版本目录
    static ref DEFAULT_PLUGIN_DIR: PathBuf = VCH_DIR.join("plugins");// 插件目录
    static ref VERSION_ENV_FILE_NAME: String = ".vch".to_string();// 环境变量文件名
    pub static ref CONTEXT: Context = Context::new(); // 全局上下文
}

#[derive(Clone, Debug, Serialize)]
pub struct Context {
    pub pwd: String,
    pub shell: Shell,
    // pub env: HashMap<String, String>,
    pub arch: ArchType,
    pub os: OsType,
    pub version_dir: PathBuf,
    pub plugin_dir: PathBuf,
    pub config: Config,
}

impl Context {
    fn new() -> Context {
        if !CONFIG_DIR.exists() {
            std::fs::create_dir_all(CONFIG_DIR.as_path()).unwrap();
        }
        if !CONFIG_FILE.exists() {
            std::fs::write(
                CONFIG_FILE.as_path(),
                toml::to_string(&Config::default()).unwrap(),
            )
            .unwrap();
        }
        let config = match std::fs::read_to_string(CONFIG_FILE.as_path()) {
            Ok(s) => toml::from_str::<Config>(s.replace("\\", "\\\\").as_str()).unwrap(),
            Err(_) => Config::default(),
        };
        let mut version_dir = DEFAULT_VERSION_DIR.clone();
        if !config.vch.versions_dir.is_empty() {
            version_dir = PathBuf::from(config.clone().vch.versions_dir);
        }
        if !version_dir.exists() {
            std::fs::create_dir_all(version_dir.as_path()).unwrap();
        }
        let mut plugin_dir = DEFAULT_PLUGIN_DIR.clone();
        if config.vch.plugin_dir != "" {
            plugin_dir = PathBuf::from(config.clone().vch.plugin_dir);
        }
        if !plugin_dir.exists() {
            std::fs::create_dir_all(plugin_dir.as_path()).unwrap();
        }
        Self {
            pwd: env::current_dir().unwrap().display().to_string(),
            shell: Shell::Unknown,
            // env:env::vars(),
            os: match env::consts::OS {
                "macos" => OsType::MacOS,
                "linux" => OsType::Linux,
                "windows" => OsType::Windows,
                "freebsd" => OsType::FreeBSD,
                "openbsd" => OsType::OpenBSD,
                "dragonfly" => OsType::DragonFlyBSD,
                "android" => OsType::Android,
                "ios" => OsType::Ios,
                _ => OsType::Unknown,
            },
            arch: match env::consts::ARCH {
                "x86" => ArchType::X86,
                "x86_64" => ArchType::X64,
                "arm" => ArchType::Arm,
                "aarch64" => ArchType::Arm64,
                "mips" => ArchType::Mips,
                "mips64" => ArchType::Mips64,
                "riscv64" => ArchType::Riscv64,
                _ => ArchType::Unknown,
            },
            version_dir,
            plugin_dir,
            config,
        }
    }

    pub fn to_string(&self) -> String {
        format!("{:?}", self)
    }
    pub(crate) fn get_terminal(&self) -> Terminal<impl Backend> {
        let terminal = init_terminal().expect("init_terminal error");
        terminal
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct Env<'a> {
    _marker: std::marker::PhantomData<&'a ()>,
}

impl<'a> Env<'a> {
    #[inline]
    pub fn get_env<K: AsRef<str>>(&self, key: K) -> Option<String> {
        env::var(key.as_ref()).ok()
    }

    #[inline]
    pub fn get_env_os<K: AsRef<str>>(&self, key: K) -> Option<OsString> {
        env::var_os(key.as_ref())
    }
}

pub fn init_error_hooks() -> color_eyre::Result<()> {
    let (panic, error) = HookBuilder::default().into_hooks();
    let panic = panic.into_panic_hook();
    let error = error.into_eyre_hook();
    color_eyre::eyre::set_hook(Box::new(move |e| {
        let _ = restore_terminal();
        error(e)
    }))?;
    std::panic::set_hook(Box::new(move |info| {
        let _ = restore_terminal();
        panic(info)
    }));
    Ok(())
}

fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
    enable_raw_mode()?;
    stdout().execute(EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout());
    let terminal = Terminal::new(backend)?;
    Ok(terminal)
}

pub(crate) fn restore_terminal() -> color_eyre::Result<()> {
    disable_raw_mode()?;
    stdout().execute(LeaveAlternateScreen)?;
    Ok(())
}
